//===--- Action.h - Abstract compilation steps ------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_DRIVER_ACTION_H
#define POLARPHP_DRIVER_ACTION_H

#include "polarphp/basic/FileTypes.h"
#include "polarphp/basic/LLVM.h"
#include "polarphp/driver/Utils.h"

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/Support/Chrono.h"

namespace llvm {
namespace opt {
class Arg;
}
}

namespace polar {
namespace driver {
class Action;

class Action {
public:
   using size_type = ArrayRef<const Action *>::size_type;
   using iterator = ArrayRef<const Action *>::iterator;
   using const_iterator = ArrayRef<const Action *>::const_iterator;

   enum class Kind : unsigned {
      Input = 0,
      CompileJob,
      InterpretJob,
      BackendJob,
      MergeModuleJob,
      ModuleWrapJob,
      AutolinkExtractJob,
      REPLJob,
      DynamicLinkJob,
      StaticLinkJob,
      GenerateDSYMJob,
      VerifyDebugInfoJob,
      GeneratePCHJob,

      JobFirst = CompileJob,
      JobLast = GeneratePCHJob
   };

   static const char *getClassName(Kind AC);

private:
   unsigned RawKind : 4;
   unsigned Type : 28;

   friend class Compilation;
   /// Actions must be created through Compilation::createAction.
   void *operator new(size_t size) { return ::operator new(size); };

protected:
   Action(Kind K, filetypes::FileTypeId Type)
      : RawKind(unsigned(K)), Type(Type) {
      assert(K == getKind() && "not enough bits");
      assert(Type == getType() && "not enough bits");
   }

public:
   virtual ~Action() = default;

   const char *getClassName() const { return Action::getClassName(getKind()); }

   Kind getKind() const { return static_cast<Kind>(RawKind); }
   filetypes::FileTypeId getType() const { return static_cast<filetypes::FileTypeId>(Type); }
};

class InputAction : public Action {
   virtual void anchor();
   const llvm::opt::Arg &Input;

public:
   InputAction(const llvm::opt::Arg &Input, filetypes::FileTypeId Type)
      : Action(Action::Kind::Input, Type), Input(Input) {}
   const llvm::opt::Arg &getInputArg() const { return Input; }

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::Input;
   }
};

class JobAction : public Action {
   TinyPtrVector<const Action *> Inputs;
   virtual void anchor();
protected:
   JobAction(Kind Kind, ArrayRef<const Action *> Inputs,
             filetypes::FileTypeId Type)
      : Action(Kind, Type), Inputs(Inputs) {}

public:
   ArrayRef<const Action *> getInputs() const { return Inputs; }
   void addInput(const Action *Input) { Inputs.push_back(Input); }

   size_type size() const { return Inputs.size(); }

   iterator begin() { return Inputs.begin(); }
   iterator end() { return Inputs.end(); }
   const_iterator begin() const { return Inputs.begin(); }
   const_iterator end() const { return Inputs.end(); }

   // Returns the index of the Input action's output file which is used as
   // (single) input to this action. Most actions produce only a single output
   // file, so we return 0 by default.
   virtual size_t getInputIndex() const { return 0; }

   static bool classof(const Action *A) {
      return (A->getKind() >= Kind::JobFirst &&
              A->getKind() <= Kind::JobLast);
   }
};

class CompileJobAction : public JobAction {
public:
   struct InputInfo {
      enum Status {
         UpToDate,
         NeedsCascadingBuild,
         NeedsNonCascadingBuild,
         NewlyAdded
      };
      Status status = UpToDate;
      llvm::sys::TimePoint<> previousModTime;

      InputInfo() = default;
      InputInfo(Status stat, llvm::sys::TimePoint<> time)
         : status(stat), previousModTime(time) {}

      static InputInfo makeNewlyAdded() {
         return InputInfo(Status::NewlyAdded, llvm::sys::TimePoint<>::max());
      }
   };

private:
   virtual void anchor();
   InputInfo inputInfo;

public:
   CompileJobAction(filetypes::FileTypeId OutputType)
      : JobAction(Action::Kind::CompileJob, None, OutputType),
        inputInfo() {}

   CompileJobAction(Action *Input, filetypes::FileTypeId OutputType, InputInfo info)
      : JobAction(Action::Kind::CompileJob, Input, OutputType),
        inputInfo(info) {}

   InputInfo getInputInfo() const {
      return inputInfo;
   }

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::CompileJob;
   }
};

class InterpretJobAction : public JobAction {
private:
   virtual void anchor();

public:
   explicit InterpretJobAction()
      : JobAction(Action::Kind::InterpretJob, llvm::None,
                  filetypes::TY_Nothing) {}

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::InterpretJob;
   }
};

class BackendJobAction : public JobAction {
private:
   virtual void anchor();

   // In case of multi-threaded compilation, the compile-action produces multiple
   // output bitcode-files. For each bitcode-file a BackendJobAction is created.
   // This index specifies which of the files to select for the input.
   size_t InputIndex;
public:
   BackendJobAction(const Action *Input, filetypes::FileTypeId OutputType,
                    size_t InputIndex)
      : JobAction(Action::Kind::BackendJob, Input, OutputType),
        InputIndex(InputIndex) {}
   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::BackendJob;
   }

   virtual size_t getInputIndex() const { return InputIndex; }
};

class REPLJobAction : public JobAction {
public:
   enum class Mode {
      Integrated,
      PreferLLDB,
      RequireLLDB
   };
private:
   virtual void anchor();
   Mode RequestedMode;
public:
   REPLJobAction(Mode mode)
      : JobAction(Action::Kind::REPLJob, llvm::None,
                  filetypes::TY_Nothing),
        RequestedMode(mode) {}

   Mode getRequestedMode() const { return RequestedMode; }

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::REPLJob;
   }
};

class MergeModuleJobAction : public JobAction {
   virtual void anchor();
public:
   MergeModuleJobAction(ArrayRef<const Action *> Inputs)
      : JobAction(Action::Kind::MergeModuleJob, Inputs,
                  filetypes::TY_PHPModuleFile) {}

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::MergeModuleJob;
   }
};

class ModuleWrapJobAction : public JobAction {
   virtual void anchor();
public:
   ModuleWrapJobAction(ArrayRef<const Action *> Inputs)
      : JobAction(Action::Kind::ModuleWrapJob, Inputs,
                  filetypes::TY_Object) {}

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::ModuleWrapJob;
   }
};

class AutolinkExtractJobAction : public JobAction {
   virtual void anchor();
public:
   AutolinkExtractJobAction(ArrayRef<const Action *> Inputs)
      : JobAction(Action::Kind::AutolinkExtractJob, Inputs,
                  filetypes::TY_AutolinkFile) {}

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::AutolinkExtractJob;
   }
};

class GenerateDSYMJobAction : public JobAction {
   virtual void anchor();
public:
   explicit GenerateDSYMJobAction(const Action *Input)
      : JobAction(Action::Kind::GenerateDSYMJob, Input,
                  filetypes::TY_dSYM) {}

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::GenerateDSYMJob;
   }
};

class VerifyDebugInfoJobAction : public JobAction {
   virtual void anchor();
public:
   explicit VerifyDebugInfoJobAction(const Action *Input)
      : JobAction(Action::Kind::VerifyDebugInfoJob, Input,
                  filetypes::TY_Nothing) {}

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::VerifyDebugInfoJob;
   }
};

class GeneratePCHJobAction : public JobAction {
   std::string PersistentPCHDir;

   virtual void anchor();
public:
   GeneratePCHJobAction(const Action *Input, StringRef persistentPCHDir)
      : JobAction(Action::Kind::GeneratePCHJob, Input,
                  persistentPCHDir.empty() ? filetypes::TY_PCH
                                           : filetypes::TY_Nothing),
        PersistentPCHDir(persistentPCHDir) {}

   bool isPersistentPCH() const { return !PersistentPCHDir.empty(); }
   StringRef getPersistentPCHDir() const { return PersistentPCHDir; }

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::GeneratePCHJob;
   }
};

class DynamicLinkJobAction : public JobAction {
   virtual void anchor();
   LinkKind Kind;

public:
   DynamicLinkJobAction(ArrayRef<const Action *> Inputs, LinkKind K)
      : JobAction(Action::Kind::DynamicLinkJob, Inputs, filetypes::TY_Image),
        Kind(K) {
      assert(Kind != LinkKind::None && Kind != LinkKind::StaticLibrary);
   }

   LinkKind getKind() const { return Kind; }

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::DynamicLinkJob;
   }
};

class StaticLinkJobAction : public JobAction {
   virtual void anchor();

public:
   StaticLinkJobAction(ArrayRef<const Action *> Inputs, LinkKind K)
      : JobAction(Action::Kind::StaticLinkJob, Inputs, filetypes::TY_Image) {
      assert(K == LinkKind::StaticLibrary);
   }

   static bool classof(const Action *A) {
      return A->getKind() == Action::Kind::StaticLinkJob;
   }
};

} // end namespace driver
} // end namespace polar

#endif
